﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

using System.Text.RegularExpressions;

namespace KingOfCSharp.iHunter.UI
{
    public partial class HtmlAndSourceTextBox : UserControl
    {
        private const string EDITOR_ID = "theTextbox";

        private bool browserReady = false;        
        private bool showHtmlSource;
        private Color borderColorFocused = SystemColors.ActiveCaption;
        private Color borderColorNonFocused = SystemColors.ControlDark;
        
        private string[] illegalPatterns = new string[] {
                @"<script.*?>",                            // all <script >
                @"<\w+\s+.*?(j|java|vb|ecma)script:.*?>",  // any tag containing *script:
                @"<\w+(\s+|\s+.*?\s+)on\w+\s*=.+?>"        // any tag containing an attribute starting with "on"
            };

		public string HtmlText
		{
			get 
			{
				string html =  this.BrowserText;
				if (null == html)
					return string.Empty;
				else
					return html;
			}
			set
			{
				if (null != value)
					this.BrowserText = value;
				else
					this.BrowserText = string.Empty;
			}
		}

		public bool Modified
		{
			get;
			set;
		}

        public HtmlAndSourceTextBox()
        {
            InitializeComponent();

            Init();
        }

        private void Init()
        {
            // Call OnPaint when resized
            this.SetStyle(ControlStyles.ResizeRedraw, true);

            // Load the initial code
            this.webBrowser_Edit.DocumentText = Properties.Resources.HtmlContent;

            this.ShowHtmlSource = false;

			this.Modified = false;

            this.ShowHideAll();
        }
        #region Text

        /// <summary>
        /// Returns the text the user edited in Html format
        /// </summary>
        [Category("Appearance")]
        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override string Text
        {
            get
            {
                return this.SourceText;
            }
            set
            {
                this.BrowserText = this.SourceText = value;
            }
        }

        [Browsable(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public new event EventHandler TextChanged
        {
            add
            {
                base.TextChanged += value;
            }
            remove
            {
                base.TextChanged -= value;
            }
        }

        [Category("Appearance")]
        public string PlainText
        {
            get
            {
                return this.webBrowser_Edit.Document.All[EDITOR_ID].InnerText;
            }
        }

        private string BrowserText
        {
            get
            {
                if (!this.browserReady)
                {
                    return null;
                }
                return this.webBrowser_Edit.Document.All[EDITOR_ID].InnerHtml;
            }
            set
            {
                if (!this.browserReady)
                {
                    return;
                }
                this.webBrowser_Edit.Document.All[EDITOR_ID].InnerHtml = value;
            }
        }

        private string SourceText
        {
            get
            {
                return this.FilterHtml(this.richTextBox_Source.Text);
            }
            set
            {
                if (value != this.richTextBox_Source.Text)
                {
                    this.richTextBox_Source.Text = value;
                }
            }
        }

        #endregion

        #region Showing, hiding syncing source <--> html


        [Category("Appearance")]
        public bool ShowHtmlSource
        {
            get
            {
                return this.showHtmlSource;
            }
            set
            {
                this.showHtmlSource = value;
                // ToDo : Change button state
                //this.tsbViewSource.Checked = value;
                this.ShowHideSourcePanel();
                if (value == false && this.ContainsFocus)
                {
                    this.webBrowser_Edit.Focus();
                }
            }
        }

        /*private void HandleFocusChanged()
        {
         

            if (this.focusHandled == this.ContainsFocus)
            {
                return;
            }
            this.ShowHideAll();
            this.Refresh();
            if (this.webBrowser_Edit.Focused)
            {
                this.webBrowser_Edit.Document.InvokeScript("InitFocus");
            }
            this.focusHandled = this.ContainsFocus;
        }*/

        private void ShowHideAll()
        {
            this.ShowHideSourcePanel();
        }

        /// <summary>
        /// Show the source panel, when
        /// <list type="bullet">
        /// <item>ShowHtmlSource is true</item>
        /// <item>this control has the focus</item>
        /// </list>
        /// </summary>
        private void ShowHideSourcePanel()
        {
            //if (this.ShowHtmlSource && this.ContainsFocusExtra)
            if (this.ShowHtmlSource)
            {
                this.splitContainer_Edit.Panel2Collapsed = false;
                if (this.splitContainer_Edit.SplitterDistance > this.ClientSize.Height)
                {
                    this.splitContainer_Edit.SplitterDistance = this.splitContainer_Edit.Height / 2;
                }
            }
            else
            {
                this.splitContainer_Edit.Panel2Collapsed = true;
            }
        }

        /// <summary>
        /// Returns true, if the browser is focused, or the browser was the last
        /// control focused, before the external toolbar was focused
        /// </summary>
        internal bool IsBrowserFocused
        {
            get
            {
                return this.webBrowser_Edit.Focused;               
            }
        }

        /// <summary>
        /// Returns true, if the source view is focused, or the source view was the
        /// last control focused before the external toolbar got the focus.
        /// </summary>
        internal bool IsSourceFocused
        {
			get
			{
				return this.richTextBox_Source.Focused;
			}
        }

        private void timer_Sync_Tick(object sender, EventArgs e)
        {
            /*// Only use CPU cycles if there is work to be performed
            if (this.focusHandled != this.ContainsFocusExtra)
            {
                this.HandleFocusChanged();
            }*/
                        
            // Synchronize the both views            
            if (this.IsBrowserFocused)
            {
                this.SyncSource();
            }
            else if (this.richTextBox_Source.Focused)
            {
                this.SyncBrowser();
            }



            // ToDo: Synchronize the buttons
            //             if (IsSourceFocused) {
            //                 this.tsbBold.Enabled = false;
            //                 this.tsbItalic.Enabled = false;
            //                 this.tsbOrderedList.Enabled = false;
            //                 this.tsbBulletList.Enabled = false;
            //                 this.tsbFont.Enabled = false;
            //                 this.tsbFontSize.Enabled = false;
            //                 this.tsbIndent.Enabled = false;
            //                 this.tsbUnIndent.Enabled = false;
            //             } else {
            //                 this.tsbBold.Enabled = true;
            //                 this.tsbItalic.Enabled = true;
            //                 this.tsbOrderedList.Enabled = true;
            //                 this.tsbBulletList.Enabled = true;
            //                 this.tsbFont.Enabled = true;
            //                 this.tsbFontSize.Enabled = true;
            //                 this.tsbIndent.Enabled = true;
            //                 this.tsbUnIndent.Enabled = true;
            //             }
            //             this.tsbBold.Checked = (bool)theBrowser.Document.InvokeScript("IsBold");
            //             this.tsbItalic.Checked = (bool)theBrowser.Document.InvokeScript("IsItalic");
            //             this.tsbOrderedList.Checked = (bool)theBrowser.Document.InvokeScript("IsOrderedList");
            //             this.tsbBulletList.Checked = (bool)theBrowser.Document.InvokeScript("IsBulletList");

            //             if (!this.tsbFont.Focused) {
            //                 this.tsbFont.Text = (theBrowser.Document.InvokeScript("GetFont") ?? string.Empty).ToString();
            //             }
            // 
            //             if (!this.tsbFontSize.Focused) {
            //                 this.tsbFontSize.Text = theBrowser.Document.InvokeScript("GetFontSize").ToString();
            //             }

        }

        /// <summary>
        /// Synchronizes the content of the browser, to the content of the source
        /// textbox.
        /// </summary>
        private void SyncBrowser()
        {
            this.BrowserText = this.SourceText;
        }

        /// <summary>
        /// Synchronizes the content of the txtSource to the browser
        /// </summary>
        private void SyncSource()
        {
            this.SourceText = this.BrowserText;
        }


        #endregion

        #region Editor Commands

        public void Cut()
        {
            if (IsSourceFocused)
            {
                this.richTextBox_Source.Cut();

                this.richTextBox_Source.Focus();
            }
            else
            {
                this.webBrowser_Edit.Document.ExecCommand("Cut", false, null);

                this.OnWebBrowserCommandExecuted();
            }
        }

        public void Paste()
        {
            if (IsSourceFocused)
            {
                this.richTextBox_Source.Paste();

                this.richTextBox_Source.Focus();
            }
            else
            {
                this.webBrowser_Edit.Document.ExecCommand("Paste", false, null);

                this.OnWebBrowserCommandExecuted();
            }
        }

        public void Copy()
        {
            if (IsSourceFocused)
            {
                this.richTextBox_Source.Copy();

                this.richTextBox_Source.Focus();
            }
            else
            {
                this.webBrowser_Edit.Document.ExecCommand("Copy", false, null);

                this.OnWebBrowserCommandExecuted();
            }
        }

        public void Undo()
        {
            if (IsSourceFocused)
            {
                this.richTextBox_Source.Undo();

                this.richTextBox_Source.Focus();
            }
            else
            {
                this.webBrowser_Edit.Document.ExecCommand("Undo", false, null);

                this.OnWebBrowserCommandExecuted();
            }
        }

        public void Redo()
        {
            if (IsSourceFocused)
            {
                this.richTextBox_Source.Redo();

                this.richTextBox_Source.Focus();
            }
            else
            {
                this.webBrowser_Edit.Document.ExecCommand("Redo", false, null);

                this.OnWebBrowserCommandExecuted();
            }
        }


        public void Bold()
        {
            this.webBrowser_Edit.Document.ExecCommand("Bold", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void Italic()
        {
            this.webBrowser_Edit.Document.ExecCommand("Italic", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void InsertOrderedList()
        {
            this.webBrowser_Edit.Document.ExecCommand("InsertOrderedList", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void InsertUnOrderedList()
        {
            this.webBrowser_Edit.Document.ExecCommand("InsertUnOrderedList", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void Outdent()
        {
            this.webBrowser_Edit.Document.ExecCommand("Outdent", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void Indent()
        {
            this.webBrowser_Edit.Document.ExecCommand("Indent", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void Underline()
        {
            this.webBrowser_Edit.Document.ExecCommand("Underline", false, null);

            this.OnWebBrowserCommandExecuted();
        }

		public void StrikeThrough()
		{
			this.webBrowser_Edit.Document.ExecCommand("StrikeThrough", false, null);

			this.OnWebBrowserCommandExecuted();
		}


        public void SetFont(string fontName)
        {
            this.webBrowser_Edit.Document.ExecCommand("FontName", false, fontName);

            this.OnWebBrowserCommandExecuted();
        }

        public void SetFontSize(int size)
        {
            this.webBrowser_Edit.Document.ExecCommand("FontSize", false, size);

            this.OnWebBrowserCommandExecuted();
        }

        public void SetForeColor(int red, int green, int blue)
        {
            this.webBrowser_Edit.Document.ExecCommand("ForeColor", false,
                string.Format("#{0}{1}{2}",
                red.ToString("X2"),
                green.ToString("X2"),
                blue.ToString("X2")
                ));

            this.OnWebBrowserCommandExecuted();
        }

        public void SetBackColor(int red, int green, int blue)
        {
            this.webBrowser_Edit.Document.ExecCommand("BackColor", false,
                string.Format("#{0}{1}{2}",
                red.ToString("X2"),
                green.ToString("X2"),
                blue.ToString("X2")
                ));

            this.OnWebBrowserCommandExecuted();
        }

        public void Superscript()
        {
            this.webBrowser_Edit.Document.ExecCommand("Superscript", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void Subscript()
        {
            this.webBrowser_Edit.Document.ExecCommand("Subscript", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void InsertHorizontalLine()
        {
            this.webBrowser_Edit.Document.ExecCommand("InsertHorizontalRule", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void FormatBlock(string tag)
        {
            this.webBrowser_Edit.Document.ExecCommand("FormatBlock", false, tag);

            this.OnWebBrowserCommandExecuted();
        }

        public void JustifyLeft()
        {
            this.webBrowser_Edit.Document.ExecCommand("JustifyLeft", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void JustifyRight()
        {
            this.webBrowser_Edit.Document.ExecCommand("JustifyRight", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void JustifyFull()
        {
            this.webBrowser_Edit.Document.ExecCommand("JustifyFull", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void JustifyCenter()
        {
            this.webBrowser_Edit.Document.ExecCommand("JustifyCenter", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void ClearFormat()
        {
            this.webBrowser_Edit.Document.ExecCommand("RemoveFormat", false, null);

            this.OnWebBrowserCommandExecuted();
        }

        public void SelectAll()
        {
            this.webBrowser_Edit.Document.ExecCommand("SelectAll", false, null);

            //this.OnWebBrowserCommandExecuted();
        }       

        #endregion

        private void OnWebBrowserCommandExecuted()
        {
            this.webBrowser_Edit.Document.InvokeScript("InitFocus");

			this.Modified = true;
        }

        #region Misc events: DocumentCompleted, OnPaint, TextChanged

        [Category("Appearance")]
        [DefaultValue(typeof(Color), "ActiveCaption")]
        public Color BorderColorFocused
        {
            get { return this.borderColorFocused; }
            set { this.borderColorFocused = value; }
        }

        [Category("Appearance")]
        [DefaultValue(typeof(Color), "ControlDark")]
        public Color BorderColorNonFocused
        {
            get { return this.borderColorNonFocused; }
            set { this.borderColorNonFocused = value; }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            if (this.ContainsFocus)
            {
                ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, this.BorderColorFocused, ButtonBorderStyle.Solid);
            }
            else
            {
                ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle, this.BorderColorNonFocused, ButtonBorderStyle.Solid);
            }
        }

        protected override void OnGotFocus(EventArgs e)
        {
            base.OnGotFocus(e);
            //this.HandleFocusChanged();
        }



        protected override void OnFontChanged(EventArgs e)
        {
            base.OnFontChanged(e);
            this.SetFontInBrowser();
        }

        private void SetFontInBrowser()
        {
            if (this.browserReady)
            {
                this.webBrowser_Edit.Document.InvokeScript("SetFont",
                    new object[] {
                    this.Font.Name,
                    this.Font.Size.ToString(System.Globalization.CultureInfo.InvariantCulture) + "pt"});
            }
        }

        #endregion

        #region Focus management
        protected override void OnEnter(EventArgs e)
        {
            base.OnEnter(e);
            //this.HandleFocusChanged();
        }

        protected override void OnLeave(EventArgs e)
        {
            base.OnLeave(e);
            //this.HandleFocusChanged();
        }
        #endregion



        /// <summary>
        /// Contains a list of regular expression that are cleared from the html.
        /// Like script, of event handlers
        /// </summary>
        [Category("Behavior")]
        public string[] IllegalPatterns
        {
            get
            {
                if (this.illegalPatterns == null)
                {
                    return new string[0];
                }
                return this.illegalPatterns;
            }
            set
            {
                // When zero length then store a null
                if (value == null || value.Length == 0)
                {
                    this.illegalPatterns = null;
                    return;
                }
                // Remove empty & duplicate strings
                List<string> buf = new List<string>();
                foreach (var item in value)
                {
                    if (!string.IsNullOrEmpty(item) && !buf.Contains(item))
                    {
                        buf.Add(item);
                    }
                }
                this.illegalPatterns = buf.Count == 0 ? null : buf.ToArray();
            }
        }

        private string FilterHtml(string original)
        {
            if (this.IllegalPatterns.Length == 0)
            {
                return original;
            }
            string buf = original;
            foreach (var pattern in this.IllegalPatterns)
            {
                Regex reg = new Regex(pattern,
                    RegexOptions.IgnoreCase |
                    RegexOptions.Multiline |
                    RegexOptions.Singleline);
                buf = reg.Replace(buf, string.Empty);
            }
            System.Diagnostics.Debug.WriteLineIf(buf != original, "Filtered: " + buf);
            return buf;
        }

        private void webBrowser_Edit_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            this.browserReady = true;
            this.SyncBrowser();
            this.SetFontInBrowser();
        }

        private void richTextBox_Source_TextChanged(object sender, EventArgs e)
        {
            this.OnTextChanged(EventArgs.Empty);
        }

        private void webBrowser_Edit_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
        {
			if (e.Control)
			{
				switch (e.KeyCode)
				{
					case Keys.X:
						this.Cut();
						break;
					case Keys.C:
						this.Copy();
						break;
					case Keys.V:
						this.Paste();
						break;
					case Keys.Z:
						this.Undo();
						break;
					case Keys.Y:
						this.Redo();
						break;
					case Keys.A:
						this.SelectAll();
						break;
				}
			}
			else if (!(e.Alt))
			{
				Modified = true;
			}
        }       	
      

    }
}
